﻿using System;
using System.Collections.Generic;
using System.Linq;
using YAMB.Common;



namespace D3.Pathing
{

    public class Path
    {
        private Queue<SpeedyAStarNode> _path;
        private Map _map;
        private SpeedyAStar _cubscout;

        private List<SpeedyAStarNode> _walkedNodes;
        private System.Drawing.Point _sCur;
        private System.Drawing.Point _sGoal;
        private System.Drawing.Point _sStart;

        public static bool IsValid = false;

        public int[] D3ToMapCoords(float x, float y)
        {
            return _map.D3ToMapCoords(x, y);
        }

        public float[] MapToD3Coords(int x, int y)
        {
            return _map.MapToD3Coords(x, y);
        }

        public int Count()
        {
            return _path.Count;
        }

        public void DrawMe()
        {
            foreach (var pf in _path)
            {
                Draw.DrawRectangle(_map.drawingCoords(pf.X, pf.Y)[0], _map.drawingCoords(pf.X, pf.Y)[1],
                    _map.drawingCoords(pf.X, pf.Y)[0] + 2, _map.drawingCoords(pf.X, pf.Y)[1] + 2, 0xFFFF69B4);
            }

        }

        public Path(float x2, float y2, SpeedyAStar cubscout, Map map) : this(Me.X, Me.X, x2, y2, cubscout, map)
        {

        }

        public System.Drawing.Point getCurNode()
        {
            return _sCur;
        }

        public Path(float x1, float y1, float x2, float y2, SpeedyAStar cubscout, Map map)
        {

            _cubscout = cubscout;
            _map = map;

            _walkedNodes = new List<SpeedyAStarNode>();

            int[] p1 = _map.D3ToMapCoords(x1, y1);
            int[] p2 = _map.D3ToMapCoords(x2, y2);


            _sStart = new System.Drawing.Point(p1[0],p1[1]);
            _sGoal  = new System.Drawing.Point(p2[0],p2[1]);

            int a = Environment.TickCount;
            List<SpeedyAStarNode> pf = cubscout.FindPath(_sStart, _sGoal);
            Game.Print("perf: "+(Environment.TickCount-a)+"ms");

            if (pf != null)
            {
                pf.Reverse();
                _path = new Queue<SpeedyAStarNode>(pf);
                IsValid = true;
            }
        }

        public SpeedyAStarNode GetNextNode()
        {
            if (!IsValid)
            {                
                Recompute();
            }

            lock(this){
            var s = _path.Peek();

            _sCur = new System.Drawing.Point(s.X,s.Y);

            _walkedNodes.Add(s);

            float[] rCoords = _map.MapToD3Coords(_sCur.X, _sCur.Y);
            return _path.Dequeue();
            }
        }

        public void Recompute()
        {

            Game.Print("Recompute");

            int[] p1 = _map.D3ToMapCoords(Me.X, Me.Y);


            _sStart = new System.Drawing.Point(p1[0], p1[1]);

            int a = Environment.TickCount;
            List<SpeedyAStarNode> pf = _cubscout.FindPath(_sStart, _sGoal);
            Game.Print("perf: " + (Environment.TickCount - a) + "ms");


            if (pf == null)
            {
                Game.Print("Couldnt find a path from: " + _sStart.X + "/" + _sStart.Y + " to " + _sGoal.X + "/" + _sGoal.Y);
                return;
            }
                IsValid = true;
                pf.Reverse();
                lock (this)
                {
                    _path = new Queue<SpeedyAStarNode>(pf);
                }

            if (_path.Count > 2)
            {
                _path.Dequeue();
                _path.Dequeue();
            }            
        }

        public void Validate(int x, int y)
        {          
            if (_path.Any(xz => xz.X == x && xz.Y == y)) 
                IsValid = false;
        }
    }

    public class Map
    {
        private const int MaximumHardness = 2;
        private static Boolean _gReplan;
        private const float CoordScale = 2.5f;
        private readonly SpeedyAStar _cubscout;

        private readonly Dictionary<System.Drawing.Point, System.Drawing.Point> border;
        private readonly PathingLogger logger = new PathingLogger("D3Pathing.Map.Log");
        private readonly List<SpeedyAStarNode> _walkedNodes;

        private List<Path> _paths;

        private int _blocked;
        private bool _stuckRecalc;

        public Map(World world)
        {
            _paths = new List<Path>();
            CachedScenes = new Dictionary<uint, CachedScene>();
            World = world;
            border = new Dictionary<System.Drawing.Point, System.Drawing.Point>();
            _walkedNodes = new List<SpeedyAStarNode>();

            Collisions = new byte[2048, 2048];

            for (int i = 0; i < 2048; i++)
            {
                for (int k = 0; k < 2048; k++)
                {
                    Collisions[i, k] = (byte) MaximumHardness;
                }
            }

            foreach (Scene s in World.GetScene())
            {
                LoadCollisions(new CachedScene(s));
            }

            _cubscout = new SpeedyAStar(Collisions);
        }

        private Dictionary<uint, CachedScene> CachedScenes { get; set; }

        private float Width { get; set; }
        private float Height { get; set; }

        private float X { get; set; }
        private float Y { get; set; }


        private int RelativeWidth { get; set; }
        private int RelativeHeight { get; set; }

        private byte[,] Collisions { get; set; }
        private World World { get; set; }

        private byte[,] getCollision()
        {
            return Collisions;
        }

        public Dictionary<uint, CachedScene> getCachedScenes()
        {
            return CachedScenes;
        }

        public void BlockRange()
        {
            int[] p = D3ToMapCoords(Me.X, Me.Y);
            int range = 7;

            for (int i = -range + 1; i < range; i++)
            {
                for (int k = -range + 1; k < range; k++)
                {
                    bool block = false;
                    for (int l = -1; l < 2; l++)
                    {
                        for (int m = - 1; m < 2; m++)
                        {
                            if (p[0] + l > 0 && p[1] + m > 0)
                                if (Collisions[p[0] + l, p[1] + m] != MaximumHardness) block = true;
                        }
                    }
                    if (block)
                    {
                        BlockNode(p[0] + i, p[1] + k, MaximumHardness, 0, 0, false);
                        //Collisions[p[0] + i, p[1] + k] = 0;
                    }
                }
            }
        }

        public void DrawMap()
        {
            foreach (System.Drawing.Point p in border.Keys)
            {
                Draw.DrawRectangle(drawingCoords(p.X, p.Y)[0], drawingCoords(p.X, p.Y)[1],
                                   drawingCoords(p.X, p.Y)[0] + 1, drawingCoords(p.X, p.Y)[1] + 1, 0xFFFFFFFF);
            }
            foreach (Path pf in _paths)
            {
                pf.DrawMe();
            }

            float[] px = drawingCoords(D3ToMapCoords(Me.X, Me.Y)[0], D3ToMapCoords(Me.X, Me.Y)[1]);

            Draw.DrawRectangle(px[0], px[1], px[0] + 5, px[1] + 5, 0xFFFF69B4);
        }

        public float[] drawingCoords(int x, int y)
        {
            var p = new float[2];
            p[1] = ((x*0.5f) - X*0.5f) + 100;
            p[0] = ((y*0.5f) - Y*0.5f) + 200;
            return p;
        }


        private void BlockNode(int x, int y, int hardness, int prevX, int prevY, bool stop)
        {
            if (hardness == 0 || x < 0 || y < 0) return;

            if (hardness == MaximumHardness) Collisions[x, y] = 0;

            if (Collisions[x, y] > 0) Collisions[x, y] = (byte) hardness;
            hardness = hardness - 1;

            var p = new System.Drawing.Point(x, y);

            if (border.Keys.Contains(p)) border.Remove(p);
            if (Collisions[x, y] == 1) border.Add(p, p);

            if (stop) return;

            stop = false;

            if (prevX != x - 1 && prevY != y + 1) BlockNode(x - 1, y + 1, hardness, x, y, stop);
            if (prevX != x && prevY != y + 1) BlockNode(x, y + 1, hardness, x, y, stop);
            if (prevX != x + 1 && prevY != y + 1) BlockNode(x + 1, y + 1, hardness, x, y, stop);
            if (prevX != x - 1 && prevY != y) BlockNode(x - 1, y, hardness, x, y, stop);
            if (prevX != x + 1 && prevY != y) BlockNode(x + 1, y, hardness, x, y, stop);
            if (prevX != x - 1 && prevY != y - 1) BlockNode(x - 1, y - 1, hardness, x, y, stop);
            if (prevX != x && prevY != y - 1) BlockNode(x, y - 1, hardness, x, y, stop);
            if (prevX != x + 1 && prevY != y - 1) BlockNode(x + 1, y - 1, hardness, x, y, stop);
        }


        public int[] D3ToMapCoords(float x, float y)
        {
            var ret = new int[2];

            ret[0] = Convert.ToInt32((x)/CoordScale);
            ret[1] = Convert.ToInt32((y)/CoordScale);

            return ret;
        }

        public float[] MapToD3Coords(int x, int y)
        {
            var ret = new float[2];

            ret[0] = (x*CoordScale);
            ret[1] = (y*CoordScale);

            return ret;
        }


        public bool LoadCollisions(CachedScene cs)
        {
            float additionalWidth = 0;
            float additionalHeight = 0;

            if (CachedScenes.ContainsKey(cs.SceneId)) return false;

            if (Width == 0 || Width < cs.X)
            {
                Width = cs.X;
                additionalWidth = cs.SizeX*CoordScale;
            }

            if (Height == 0 || Height < cs.Y)
            {
                Height = cs.Y;
                additionalHeight = cs.SizeY*CoordScale;
            }


            Width = (Width - X) + additionalWidth;
            Height = (Height - Y) + additionalHeight;

            RelativeWidth = Convert.ToInt32(Width/CoordScale);
            RelativeHeight = Convert.ToInt32(Height/CoordScale);

            CachedScenes.Add(cs.SceneId, cs);
            bool replan = false;

            float x = cs.X;
            float y = cs.Y;

            int relativeX = 0;
            int relativeY = 0;

            if (x > 0)
            {
                relativeX = Convert.ToInt32(x/CoordScale);
            }
            if (y > 0)
            {
                relativeY = Convert.ToInt32(y/CoordScale);
            }

            if (X == 0 || X > relativeX)
            {
                X = relativeX;
            }

            if (Y == 0 || Y > relativeY)
            {
                Y = relativeY;
            }

            for (int x2 = 0; x2 < cs.SizeX; x2++)
            {
                for (int y2 = 0; y2 < cs.SizeY; y2++)
                {
                    int xn = relativeX + x2;
                    int yn = relativeY + y2;
                    if (!cs.Collisions[x2, y2].HasFlag(NavMeshFlags.ALLOW_WALK))
                    {
                        foreach (Path p in _paths)
                        {
                            p.Validate(xn, yn);
                        }
                       
                        BlockNode(xn, yn, MaximumHardness, 0, 0, false);
                        _blocked++;
                    }
                }
            }


            return true;
        }

        public Path ComputePath(Point point)
        {
            return ComputePath(Unit.LocalPlayer.X, Unit.LocalPlayer.Y, point.X, point.Y);
        }

        public Path ComputePath(float x, float y)
        {
            return ComputePath(Unit.LocalPlayer.X, Unit.LocalPlayer.Y, x, y);
        }

        public Path ComputePath(float x1, float y1, float x2, float y2)
        {
           var x = new Path(x1,y1,x2,y2,_cubscout,this);

             _paths.Add(x);
            return x;
        }

        #region Nested type: mPoint

        public struct mPoint
        {
            public int x;
            public int y;
        }

        #endregion
    }
}